package org.geekbang.thinking.in.spring.bean.scope;

import org.geekbang.thinking.in.spring.ioc.overview.domain.User;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;

import java.util.Map;

/**
 * Bean 的作用域
 */
public class BeanScopeDemo {

    @Bean   // 默认 singleton
    public static User singletonUser() {
        return createUser();
    }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public static User prototypeUser() {
        return createUser();
    }

    private static User createUser() {
        User user = new User();
        user.setId(System.nanoTime());
        return user;
    }

    @Autowired
    @Qualifier("singletonUser")
    private User singletonUser;

    @Autowired
    @Qualifier("singletonUser")
    private User singletonUser1;

    @Autowired
    @Qualifier("prototypeUser")
    private User prototypeUser;

    @Autowired
    @Qualifier("prototypeUser")
    private User prototypeUser1;

    @Autowired
    @Qualifier("prototypeUser")
    private User prototypeUser2;

    @Autowired
    private Map<String, User> userMap;

    public static void main(String[] args) {
        // 1、创建 BeanFactory 容器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        // 2、注册 Configuration Class（配置类） -> Spring Bean
        context.register(BeanScopeDemo.class);

        // 不建议这么实现
        context.addBeanFactoryPostProcessor(beanFactory -> {
            beanFactory.addBeanPostProcessor(new BeanPostProcessor() {
                @Override
                public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                    System.out.printf("%s Bean 名称：%s 在初始化后回调...%n", bean.getClass().getName(), beanName);
                    return bean;
                }
            });
        });

        // 3、启动 Spring 应用上下文
        context.refresh();

        // 结论一：
        // Singleton Bean 无论依赖查找还是依赖注入，均为同一个对象
        // Prototype Bean 无论依赖查找还是依赖注入，均为新生成的对象

        // 结论二：
        // 如果依赖注入集合类型的对象， Singleton Bean 和 Prototype Bean 均会存在一个
        // Prototype Bean 有别于其他地方的依赖注入 Prototype Bean

        // 结论三：
        // 无论是 Singleton Bean 还是 Prototype Bean 均会执行初始化方法回调
        // 不过 Singleton Bean 会执行销毁方法的回调，但 Prototype Bean 不会执行销毁方法的回调

        // scopedBeansByLookUp(context);

        scopedBeansByInjection(context);

        ///5、关闭 Spring 应用上下文
        context.close();

    }

    private static void scopedBeansByInjection(AnnotationConfigApplicationContext context) {
        BeanScopeDemo beanScopeDemo = context.getBean(BeanScopeDemo.class);
        System.out.println("beanScopeDemo.singletonUser: " + beanScopeDemo.singletonUser);
        System.out.println("beanScopeDemo.singletonUser1: " + beanScopeDemo.singletonUser1);
        System.out.println("beanScopeDemo.prototypeUser: " + beanScopeDemo.prototypeUser);
        System.out.println("beanScopeDemo.prototypeUser1: " + beanScopeDemo.prototypeUser1);
        System.out.println("beanScopeDemo.prototypeUser2: " + beanScopeDemo.prototypeUser2);

        System.out.println("beanScopeDemo.userMap: " + beanScopeDemo.userMap);
    }

    private static void scopedBeansByLookUp(AnnotationConfigApplicationContext context) {
        for (int i = 0; i < 3; i++) {
            // singletonUser 是共享的 Bean 对象
            User singletonUser = context.getBean("singletonUser", User.class);
            System.out.println("singletonUser: " + singletonUser);
            // prototypeUser 是每次依赖查找均生成了新的 Bean 对象
            User prototypeUser = context.getBean("prototypeUser", User.class);
            System.out.println("prototypeUser: " + prototypeUser);
        }
    }

}
